"""
框架的所有组件的基础功能。
"""
import asyncio
import typing
from google.protobuf.message import DecodeError, EncodeError
import grpc  # type: ignore

from .framework import Session
from .framework import dispatch, get_event_loop
from .utils import Any, Empty, StringValue, Int32Value, from_any, to_any
from ..logger import logger
from ..proto import Interaction  # type: ignore

BaseSessionType = typing.TypeVar("BaseSessionType", bound='BaseSession')
BaseSessionParentType = typing.TypeVar("BaseSessionParentType", bound='BaseSession')


class BaseSession(Session):
    """
    Elegant-IO框架使用会话表示一路通信，每一个组件都要基于本类。
    本类不对外公开，仅用于框架层的抽象表示。
    实现了异步通信的数据解耦，封装成异步调用的语法形式。
    """

    def __init__(self, context, *args, **kwargs):  # type: ignore
        """
        :param context: grpcio框架的context对象，或者是socketio框架的客户端连接信息字典
        """
        super().__init__(context)
        self._futures: typing.Dict[int, typing.Optional[asyncio.Future]] = {}

    def __del__(self):
        """
        如果会话被销毁，这边做清理内存的工作。
        """
        if hasattr(self, '_futures'):
            self._futures.clear()
            del self._futures
        super().__del__()

    def create_callback(
            self, func: typing.Optional[typing.Awaitable | typing.Callable], *argv
    ) -> typing.Union[Any, Empty]:
        """
        如果某个会话需要使用callback模式来接受设备的组件事件，就需要调用这个方法把python的函数对象序列化成protobuf的对象。
        同时此函数还会创建future对象，当接收到设备的组件消息，请给future对象设置数据。
        :param func: 异步函数或future对象。
        """
        if not func:
            return to_any(None)
        session = func.__self__ if hasattr(func, '__self__') else self
        session = session if isinstance(session, Session) else self
        future_id, future = session.create_future(func)
        return to_any(future, Interaction, name='callback', session=session.id, future=future_id, argv=argv)

    def remove_callback(self, func: typing.Callable):
        """
        移除从create_callback函数创建的callback。
        :param func: 异步函数或future对象。
        """
        session = func.__self__ if hasattr(func, '__self__') else self
        session = session if isinstance(session, Session) else self
        for k, v in session._futures.items():
            if v == func:
                del session._futures[k]
                break

    def create_future(
            self, future: typing.Optional[typing.Awaitable[asyncio.Future]] = None
    ) -> typing.Tuple[int, typing.Awaitable[asyncio.Future]]:
        """
        创建一个future对象，这个对象可以被await关键字等待，当future得到了数据，await所在的代码位置将从暂停状态恢复。
        由于使用了python的id函数标识future的唯一性，在某些情况下id函数不是安全的（例如当没有任何变量引用future对象时，future对象将被自动回收，这时候如果从设备端接收到这个id值并尝试使用id函数的相反操作时，会导致程序因为内存访问错误而崩溃），所以此方法使用了一个字典对象自行维护了future对象的生命周期，从而确保内存访问是安全的。
        :param future: future对象，如果提供这个值，本函数将直接返回而不自动创建全新的future对象。
        """
        _future = future if future else get_event_loop().create_future()
        future_id = id(_future)
        self._futures[future_id] = _future
        return future_id, _future

    def find_future_by_id(self, future_id: int) -> typing.Union[asyncio.Future, None]:
        """
        通过future_id查找一个future对象，这通常放在接收到设备的消息、即将给某个future设置数据时使用。
        调用此方法是安全的。
        :param future_id: future的对象ID。
        """
        if future_id in self._futures:
            return self._futures[future_id]
        return None

    def remove_future_by_id(self, future_id: int):
        """
        删除一个future对象。
        :param future_id: future对象的ID。
        """
        if future_id in self._futures:
            del self._futures[future_id]

    @classmethod
    async def create(
            cls: typing.Type[BaseSessionType], parent: BaseSessionParentType, *args: typing.Any, **kwargs: typing.Dict
    ) -> BaseSessionType:
        """
        创建一个会话新实例，通知设备端创建相关组件。
        :param parent: 要基于哪一个实例创建。
        """
        if not isinstance(parent, BaseSession):
            raise TypeError(
                'Param `parent` must be created by subclass of BaseSession, instead of %s.' % parent.__class__.__name__
            )
        cls_name = to_any(getattr(cls, 'name'))
        return await parent.send_action_until_return('create', cls_name, *args)

    async def destroy(self):
        """
        销毁会话实例。
        """
        await self.send_action("destroy")
        await super().destroy()

    async def on_callback(self, cont, *args, **kwargs):
        """
        当接收到组件的一个callback消息后自动调用此方法。
        这会通知python通过future保存的相关函数。
        """
        if self.is_debug:
            logger.debug('%s: onCallback.' % self.__class__.__name__)
        session_id = kwargs['sessionId'] if 'sessionId' in kwargs else None
        if not session_id:
            self.send_callback_result(cont, None)
            return
        session = self.find_session_by_id(session_id)
        future_id = kwargs['futureId'] if 'futureId' in kwargs else 0
        future = session.find_future_by_id(future_id)
        res = await future(*args)
        self.send_callback_result(cont, res)

    async def on_receive(self, msg: Interaction):
        """
        当接收到设备组件的任何信息会自动调用此方法，在这里进行相关事件的转发，这包括组件的生命周期函数。
        :param msg: 接收到的交互动作数据。
        """
        if msg.name == 'onCallback':
            dispatch(self.on_callback(*msg.argv, sessionId=msg.session, futureId=msg.future))
        elif msg.name == 'onCreate':
            dispatch(self.on_create(*msg.argv, sessionId=msg.session, futureId=msg.future))
        elif msg.name == 'onDestroy':
            dispatch(self.on_destroy())
        if msg.name == 'onError':
            dispatch(self.on_error(msg.argv[0], sessionId=msg.session, futureId=msg.future))
        else:
            await super().on_receive(msg)

    async def on_create(self, *args: typing.Any, **kwargs: typing.Any):
        """
        当接收到设备的组件创建事件后自动调用该方法。
        在本方法中我们会发送一个ready交互动作，通知设备端已经完成所有握手，表示会话实例已经整装待发。
        多数的会话实例（Activity除外）是通过服务端创建的，通常会写成
        x = await X.create(parent)
        为了让x变量能接收到实例对象，因此我们在本方法中给future对象设置了结果。
        """
        await super().on_create()
        dispatch(self.send_action('ready', self.reference))
        session_id = kwargs['sessionId'] if 'sessionId' in kwargs else None
        if not session_id:
            return
        future_id = kwargs['futureId'] if 'futureId' in kwargs else 0
        self.set_action_result(session_id, future_id, self)

    async def on_destroy(self):
        """
        当设备的组件销毁后自动调用此方法。
        """
        await super().on_destroy()

    async def on_error(self, exception: typing.Union[Exception, Any], **kwargs):
        """
        当组件发生错误后自动调用此方法。
        :param exception: 错误信息。
        """
        if isinstance(exception, Any):
            errmsg = from_any(exception, StringValue)
            exception = RuntimeError(errmsg)
        await super().on_error(exception, **kwargs)
        session_id = kwargs['sessionId'] if 'sessionId' in kwargs else None
        if session_id:
            future_id = kwargs['futureId'] if 'futureId' in kwargs else 0
            self.set_action_exception(session_id, future_id, exception)

    async def send_action_until_return(self, name: str, *argv) -> typing.Any:
        """
        网设备端发送交互动作，并等待设备端的响应结果。
        :param name: 动作名称。
        """
        future_id, future = self.create_future()
        dispatch(self.send_action(name, *argv, session_id=self.id, future_id=future_id))
        if self.is_debug:
            arg: str = ''
            try:
                arg = from_any(argv[0], StringValue) if argv else ''
            except (DecodeError, EncodeError):
                pass
            logger.debug('%s: Send %s %s' % (self.__class__.__name__, name, arg,))
            res = await future
            logger.debug("%s: Recv %s %s %s" % (self.__class__.__name__, name, arg, res,))
        else:
            res = await future

        self.remove_future_by_id(future_id)
        return res

    async def send_action(self, name: str, *argv, session_id: int = 0, future_id: int = 0):
        """
        网设备端发送交互动作。
        :param name: 动作名称。
        :param session_id: 会话ID。
        :param future_id: future的对象ID。
        """
        action = Interaction(name=name, session=session_id, future=future_id, argv=argv)
        return await self.send(action)

    def set_action_result(self, session_id: int, future_id: int, value: typing.Any):
        """
        当发送了某个动作后，可以使用此函数给动作设置结果。
        :param session_id: 会话ID。
        :param future_id: future的对象ID。
        :param value: 要设置的数据。
        """
        session = self.find_session_by_id(session_id)
        if not session:
            return
        future = session.find_future_by_id(future_id)
        if future:
            future.set_result(value)

    def set_action_exception(self, session_id: int, future_id: int, value: Exception):
        """
        当发送了某个动作后，如果在设备端抛出了错误，则需要通知future，否则future将无法继续运行。
        :param session_id: 会话ID。
        :param future_id: future的对象ID。
        :param value: 要设置的异常信息。
        """
        session: BaseSessionType = self.find_session_by_id(session_id)
        if not session:
            return
        future = session.find_future_by_id(future_id)
        if future:
            future.set_exception(value)

    def send_callback_result(self, cont, result, result_class=None):
        """
        当接收到设备端的回调并执行，发回一个结果。
        :param cont: 对应设备端可以往后继续执行代码的ID，通常是一个携程被暂停后生成的continuation对象ID。
        :param result: 要发回给设备端的函数返回值。
        :param result_class: 返回值对应的protobuf类型。
        """
        if self.is_debug:
            logger.debug("%s: Send CallbackResult %s %s" % (
                self.__class__.__name__, from_any(cont, Int32Value), str(result),
            ))
        dispatch(self.send_action('callbackResult', cont, to_any(result, result_class)))
